using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using mb;

namespace mb
{

    public static class Http
    {
        public class Option
        {
            public string Url;
            public HttpMethod Method;
            public string Body;
            public string ContentType = "text/plain";
            public int TimeoutSec;
            public Action<long, long> OnProgress;
            public Action<bool, int, string> OnFinish;
            public string SaveAs;
            public HttpResponseMessage Response;
            public int PosFrom;
            public int PosTo;

            public Option()
            {
                Method = HttpMethod.Get;
                TimeoutSec = 0;
                PosFrom = PosTo = -1;
            }
        }

        static HttpClient httpClient;
        static ILog logger = mb.ServiceLocator.Instance.Get<ILog>();

        static Http()
        {
            httpClient = new HttpClient();
        }

        public async static Task < (int, string) > MakeRequest(Option opt)
        {
            var request = new HttpRequestMessage
            {
                RequestUri = new System.Uri(opt.Url),
                Method = opt.Method,
            };

            try
            {
                if (opt.TimeoutSec > 0)
                    httpClient.Timeout = TimeSpan.FromSeconds(opt.TimeoutSec);

                if (opt.Body != null)
                {
                    request.Content = new StringContent(opt.Body, Encoding.UTF8, opt.ContentType);
                }

                if (opt.PosFrom != -1)
                {
                    if (opt.PosTo != -1)
                        request.Headers.Add("range", $"bytes={opt.PosFrom}-{opt.PosTo}");
                    else
                        request.Headers.Add("range", $"bytes={opt.PosFrom}-");
                }

                var response = await httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
                // response.EnsureSuccessStatusCode();
                opt.Response = response;

                string result = null;
                var totalBytes = response.Content.Headers.ContentLength;
                using(var contentStream = await response.Content.ReadAsStreamAsync())
                {
                    // logger.Log("http read stream", totalBytes, response.StatusCode);

                    var totalBytesRead = 0L;
                    var readCount = 0L;
                    var buffer = new byte[16 * 1024];
                    var isMoreToRead = true;

                    FileStream fileStream = null;
                    MemoryStream memStream = null;
                    if (opt.SaveAs != null)
                        fileStream = new FileStream(opt.SaveAs, FileMode.Create, FileAccess.Write, FileShare.None, buffer.Length, true);
                    else
                        memStream = new MemoryStream();

                    do
                    {
                        var bytesRead = await contentStream.ReadAsync(buffer, 0, buffer.Length);
                        if (bytesRead == 0)
                        {
                            isMoreToRead = false;
                            opt.OnProgress?.Invoke((long) totalBytes, (long) totalBytes);
                            break;
                        }

                        if (fileStream != null)
                            fileStream.WriteAsync(buffer, 0, bytesRead);
                        else
                            memStream.WriteAsync(buffer, 0, bytesRead);

                        totalBytesRead += bytesRead;
                        readCount += 1;

                        opt.OnProgress?.Invoke((long) totalBytes, (long) totalBytes);
                    }
                    while (isMoreToRead);

                    if (fileStream != null)
                    {
                        fileStream.Close();
                    }
                    else
                    {
                        result = Encoding.UTF8.GetString(memStream.ToArray());
                    }
                }

                opt.OnFinish?.Invoke(true, (int) response.StatusCode, result);
                return ((int) response.StatusCode, result);
            }
            catch (System.Exception e)
            {
                opt.OnFinish?.Invoke(false, 0, e.ToString());
                return (0, e.ToString());
            }

        }

    }
}